/*
 * Author: nicolas.bredeche(@)upmc.fr
 * Created on 2005-08-31 -- Refactored on 2014-01-06 
 */

package picoevo.core;

import java.util.ArrayList;
import java.util.Arrays;
//For parallel execution
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import picoevo.tools.*;

// note: the abstract prefix is to avoid direct use in an application
public class Population extends PopulationObject implements PopulationInterface {

	// ### Data ###

	protected int _generation = 0; // how old is the population
	protected boolean _reevaluate = true; // re-evaluate individual? (e.g. with elitist scheme, individuals may or may not be reevaluate
	protected ParameterSet _properties = null;
	protected int maxtime = 60; //maximum waiting time expressed in seconds
	
	
	// ### Constructor(s) ###
	
	public Population ( String __populationName )
	{
		super(__populationName);
	}

	// ### Other methods ###

	/** get age of the population (i.e. number of times it has been "renewed") */
	public int getGenerationNumber()
	{
		return _generation;
	}
	
	/** reset age of the population to zero */
	public void resetGenerationNumber()
	{
		_generation = 0;
	}

	/** add the given individual to this population */
	public void add ( Individual __ind )
	{
		this._individuals.add(__ind);
	}
	
	/** initialize the population */
	public void initialize()
	{
		Display.critical("Population::init() is empty. Derive a new class or initialize by hand.");
	}


	/** Perform evaluation for the population (i.e. call Individual.performEvaluation method for each individual) 
	 *  return: number of individuals actually evaluated (ie. with fitness function computed)
	 * */
	public int evaluate()
	{
		if ( Misc.multithread )
			return evaluate_multithread();
		else
			return evaluate_singlethread();
	}
	
	private int evaluate_singlethread()
	{
		int evaluationCnt = 0;
		for ( int i = 0 ; i != _individuals.size() ; i++ )
		{
			if ( _individuals.get(i).isEvaluated() == false || this._reevaluate == true )
			{
				_individuals.get(i).evaluate();
				evaluationCnt++;
			}
		}
		return evaluationCnt;
	}
	
	private int evaluate_multithread()
	{	
		boolean debug = false;
		
		ThreadPoolExecutor eservice;
		CompletionService<Boolean> cservice;
		eservice = (ThreadPoolExecutor) Executors.newFixedThreadPool(Misc.getNumberOfAvailableProcessors());
		cservice = new ExecutorCompletionService<Boolean>(eservice);
		
		int evaluationCnt = 0;
		ArrayList<Future<Boolean>> futures = new ArrayList<Future<Boolean>>();
		final ArrayList<Integer> launched = new ArrayList<Integer>();
		
		for ( int i = 0 ; i != _individuals.size() ; i++ )
		{
			if ( _individuals.get(i).isEvaluated() == false || this._reevaluate == true )
			{
				final int ref = i;
				Callable<Boolean> eval = new Callable<Boolean>(){

					@Override
					public Boolean call() throws Exception {
						launched.add(ref);
						_individuals.get(ref).evaluate();
						return true;
					}
					
				};
				Future<Boolean> fut = cservice.submit(eval);
				
				futures.add(fut);
				evaluationCnt++;
			}
		}
		
		for (int i = 0; i < evaluationCnt; i++) {
			try {
				
				Future<Boolean> future = cservice.poll(maxtime, TimeUnit.SECONDS);
				
				
				if (future == null) {
					int canc = 0;
					if(debug){
						System.out.println("Time out: cancelling threads");
					}
					for (int index : launched) {
						Future<Boolean> future1 = futures.get(index);
						
						if (future1 != null &&  !future1.isDone()) {
							System.out.println("Cancelling "+index);
							boolean cancelled = future1.cancel(true); //We clean up hard.
							
							if (!cancelled){
								System.err.println("[ERROR] Couldn't cancel thread");
							} else {
								if(debug){
									canc++;
								}
							}
						}
					}
					eservice.purge();
					if(debug){
						System.out.println("Cancelled "+canc+" threads");
						System.out.println("Threads available: "+eservice.getPoolSize());
					}
					
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		eservice.shutdownNow();
		
		//System.gc(); // slows down.
		
		return evaluationCnt;
	}

	/**
	 * Renew the population (i.e. individuals list) by replacing it with the content of the target PopulationContainer.
	 * 
	 * warning (1): after this call, the PopulationContainer passed as parameter does not contain any individuals to ensure
  	 * single-referenced individuals
	 * warning (2): at the end of this method, we assume that there will be no pointer to any of the objects previously
	 * contained in _individualList so as to let all of these to be garbage collected - In practical, this means that
	 * the programmer should pay special attention to suppress any PopulationContainer and/or PopulationViewer that
	 * is not needed (set embedded IndividualList explicitly to "null" ) - e.g. The PopulationContainer parameter
	 * should be set to null after calling this method.
	 * 
	 * dev note : final class for the moment.
	 * 
	 * 	 
	 * @param newpop
	 */
	final public void renew(PopulationContainer __newPopulationContainer) 
	{
		if ( __newPopulationContainer == null || __newPopulationContainer.getSize() == 0 )
			Display.critical("[\""+this.getName()+"\"] "+this.getClass().getName()+"::renewPopulation - new population is empty!");
		this._individuals = __newPopulationContainer.getPopulation();
		for ( int i = 0 ; i != this.getSize() ; i++ )
		{
			if ( this._reevaluate == true )
				this.getIndividual(i).setEvaluationFlag(false);
		}
		__newPopulationContainer.reset();
		this._generation++;
	}

	/**
	 * Display population statistics (average and best individuals)
	 *
	 */
	public void displayStatistics() 
	{
		((StatisticsOperator)this.getProperties().getObjectProperty("PopulationStatisticsOperator")).displayStatistics(this);
	}

	/**
	 * Log population statistics (average and best individuals) into a file (as given in the parameter set)
	 *
	 */
	public void logStatistics()
	{
		if ( this.getProperties().contains("LogFilename") )
			((StatisticsOperator)this.getProperties().getObjectProperty("PopulationStatisticsOperator")).logStatistics(this,this.getProperties().getStringProperty("LogFilename"));
		if ( this.getProperties().contains("DetailedLogFilename") )
			((StatisticsOperator)this.getProperties().getObjectProperty("PopulationStatisticsOperator")).logIndividuals(this,this.getProperties().getStringProperty("DetailedLogFilename"));
	}
	
	/** 
	 * return actual population size
	 */
	public int getSize ()
	{
		if ( this._individuals == null )
			Display.error(this.getClass().getName()+" : trying to get size of population while population not initialised.");
		return ( this._individuals.size() );
	}
	
	// ### Other methods ###

	public void displayInformation()
	{
		Display.info("### Population Information : summary ###");
		Display.info("Population name : " + this.getName());
		Display.info("Population size : " + this.getSize() );
	    Display.info("\n");
	    for ( int i = 0 ; i != _individuals.size() ; i++ )
	    {
	    	this.getIndividual(i).displayInformation();
	    	Display.info("");
	    }
	}

	public void displayInformationCompact()
	{
		Display.info("### Population Information : summary ###");
		Display.info("Population name : " + this.getName());
		Display.info("Population size : " + this.getSize() );
	    Display.info("Individuals:");

	    for ( int i = 0 ; i != _individuals.size() ; i++ )
	    {
	    	Display.info("\t " + this.getIndividual(i).getId() + " ( fitness value: " + this.getIndividual(i).getFitness() + ").");
	    }
	}


	final public ParameterSet getProperties() 
	{
		return (_properties);
	}

	public void setProperties(ParameterSet __properties) 
	{
		_properties = __properties;
	}

	public void setReevaluate(boolean __flag) 
	{
		this._reevaluate = __flag;	
	}

}
